﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using GMap.NET;
using GMap.NET.WindowsPresentation;

namespace MyPoints.UserControls
{
  /// <summary>
  /// Interaction logic for Map.xaml
  /// </summary>
  public partial class Map : UserControl
  {
    #region Nested types

    private struct ERectLatLng
    {
      private double minLat;
      private double minLng;
      private double maxLat;
      private double maxLng;

      public ERectLatLng(RectLatLng viewArea) : this()
      {
        this.minLat = viewArea.Lat;
        this.minLng = viewArea.Lng;
        this.maxLat = viewArea.Lat + viewArea.HeightLat;
        this.maxLng = viewArea.Lng + viewArea.WidthLng;
      }

      public PointLatLng Location
      {
        get
        {
          PointLatLng ret = new PointLatLng(minLat, minLng);
          return ret;
        }
      }
      public SizeLatLng Size
      {
        get
        {
          SizeLatLng ret = new SizeLatLng(
            maxLat - minLat,
            maxLng - minLng);
          return ret;
        }
      }

      public void Init(PointLatLng p)
      {
        minLat = p.Lat;
        maxLat = minLat;
        minLng = p.Lng;
        maxLng = minLng;
      }

      public void Extend(PointLatLng p)
      {
        if (p.Lat < minLat)
          minLat = p.Lat;
        if (p.Lat > maxLat)
          maxLat = p.Lat;
        if (p.Lng < minLng)
          minLng = p.Lng;
        if (p.Lng > maxLng)
          maxLng = p.Lng;
      }

      public PointLatLng GetCenter()
      {
        PointLatLng ret = new PointLatLng(
          (maxLat + minLat) / 2,
          (maxLng + minLng) / 2
          );
        return ret;
      }
      public bool IsSmall()
      {
        return
          Math.Abs(maxLat - minLat) < 1
          &&
          Math.Abs(maxLng - minLng) < 1;
      }

      internal bool IsIn(ERectLatLng biggerArea)
      {
        return
          this.minLat > biggerArea.minLat
          &&
          this.maxLat < biggerArea.maxLat
          &&
          this.minLng > biggerArea.minLng
          &&
          this.maxLng < biggerArea.maxLng;
      }

      public override string ToString()
      {
        return string.Format("{0}-{1} x {2}-{3}", minLat, maxLat, minLng, maxLng);
      }

      internal bool IsIn(RectLatLng viewArea)
      {
        bool ret =
          viewArea.LocationTopLeft.Lat > maxLat && viewArea.HeightLat > (maxLat - minLat);
        ret &=
          viewArea.LocationTopLeft.Lng < minLng && viewArea.WidthLng > (maxLng - minLng);
        return ret;
      }
    }

    #endregion Nested types

    #region Fields & Properties

    private Dictionary<wptType, GMapMarker> markers = new Dictionary<wptType, GMapMarker>();
    private wptType selected = null;

    private Dictionary<Color, BitmapSource> iconsUnselected = new Dictionary<Color, BitmapSource>();
    private Dictionary<Color, BitmapSource> iconsSelected = new Dictionary<Color, BitmapSource>();

    public PointLatLng Center
    {
      get
      {
        return map.Position;
      }
    }

    #endregion Fields & Properties

    #region Events

    public delegate void WptSelectedHandler(Map sender, wptType wpt);
    public event WptSelectedHandler WptSelected;

    public delegate void NewWptRequestHandler(Map sender, PointLatLng point);
    public event NewWptRequestHandler NewWptRequest;

    #endregion Events

    #region Public methods + .ctor

    public Map()
    {
      InitializeComponent();
    }

    public void Init()
    {
      map.MapProvider = GMap.NET.MapProviders.BingMapProvider.Instance;

      GMap.NET.GMaps.Instance.Mode = GMap.NET.AccessMode.ServerOnly;
      map.Position = new GMap.NET.PointLatLng(
        Shared.sett.user.map.initialLatitude,
        Shared.sett.user.map.initialLongitude);
      map.MinZoom = Shared.sett.user.map.minZoom;
      map.MaxZoom = Shared.sett.user.map.maxZoom;
      map.Zoom = Shared.sett.user.map.initialZoom;
      map.ShowCenter = false;

      map.DragButton = MouseButton.Left;


      sldZoom.DataContext = map;

      map.MouseUp += Map_MouseUp;
    }

    public void SelectWpt(wptType wpt)
    {
      GMapMarker mrk = markers[wpt];

      _UnselectMarker();
      _SelectMarker(wpt);
    }

    public void AddWpts(IEnumerable<wptType> wpts)
    {
      foreach (var wpt in wpts)
      {
        AddWpt(wpt);
      }
    }

    public void AddWpt(wptType wpt)
    {
      _DrawWpt(wpt, false);
      wpt.LocationChanged += wpt_LocationChanged;
    }

    private void wpt_LocationChanged(wptType sender)
    {
      _EraseWpt(sender);
      _DrawWpt(sender, sender == selected);
    }

    public void RemoveWpt(wptType wpt)
    {
      wpt.LocationChanged -= wpt_LocationChanged;
      _EraseWpt(wpt);
      if (wpt == this.selected)
        this.selected = null;
    }

    public void ClearWpts()
    {
      var tmp = markers.Keys.ToList();
      foreach (var wpt in tmp)
      {
        RemoveWpt(wpt);
      }
    }


    public void UpdateWptsTitle(wptType sender)
    {
      _EraseWpt(sender);
      _DrawWpt(sender, sender == selected);
    }

    public void UpdateWptsCategory(wptType sender)
    {
      _EraseWpt(sender);
      _DrawWpt(sender, sender == selected);
    }

    #endregion Public methods + .ctor

    #region Private methods

    private GMapMarker CreateMarker(wptType wpt, bool isSelected)
    {
      Color fillColor = Shared.GetMarkerFillColorForWptType(wpt);
      if (iconsUnselected.ContainsKey(fillColor) == false)
      {
        BuildIconsForColor(fillColor);
      }

      markerType markerSett = isSelected ?
        Shared.sett.user.map.selectedMarker : Shared.sett.user.map.marker;

      GMapMarker ret;
      PointLatLng point = new PointLatLng((double)wpt.lat, (double)wpt.lon);
      ret = new GMapMarker(point);

      MapIcon mapIcon = new MapIcon();
      
      BitmapSource bmps;
      if (isSelected)
        bmps = iconsSelected[fillColor];
      else
        bmps = iconsUnselected[fillColor];

      mapIcon.icon.Source = bmps;
      ret.Shape = mapIcon;
      ret.Offset = new Point(
        markerSett.offsetX,
        markerSett.offsetY);

      Label lbl = new Label();
      lbl.Content = wpt.name;
      mapIcon.ToolTip = lbl;

      mapIcon.Tag = wpt;
      mapIcon.MouseUp += MapIcon_MouseUp;

      return ret;
    }

    private void BuildIconsForColor(Color fillColor)
    {
      markerType markerSett;
      Uri uri;
      BitmapImage bmp;
      BitmapSource bmps;


      markerSett = Shared.sett.user.map.selectedMarker;
      uri = new Uri(
          markerSett.imageUrl, UriKind.Relative);
      bmp = new BitmapImage(uri);
      bmps = Shared.UpdateBitmap(bmp, fillColor);
      iconsSelected[fillColor] = bmps;



      markerSett = Shared.sett.user.map.marker;
      uri = new Uri(
          markerSett.imageUrl, UriKind.Relative);
      bmp = new BitmapImage(uri);
      bmps = Shared.UpdateBitmap(bmp, fillColor);
      iconsUnselected[fillColor] = bmps;
    }

    #endregion Private methods

    #region Map event handlers

    private void Map_MouseUp(object sender, MouseButtonEventArgs e)
    {
      if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control) == false)
        return;

      System.Windows.Point p = e.GetPosition(map);
      PointLatLng point = map.FromLocalToLatLng((int)p.X, (int)p.Y);

      if (NewWptRequest != null)
      {
        NewWptRequest(this, point);
      }
    }

    private void MapIcon_MouseUp(object sender, MouseButtonEventArgs e)
    {
      MapIcon icon = sender as MapIcon;
      wptType wpt = icon.Tag as wptType;

      _UnselectMarker();
      _SelectMarker(wpt);

      if (WptSelected != null)
        WptSelected(this, wpt);
    }

    private void map_MouseWheel(object sender, MouseWheelEventArgs e)
    {
      if (e.Delta > 0)
        map.Zoom = map.Zoom + 1;
      else
        map.Zoom = map.Zoom - 1;
    }

    #endregion Map event handlers

    #region Map drawing methods

    private void _SelectMarker(wptType wpt)
    {
      if (this.selected != null)
        throw new ApplicationException("Cannot select multiple markers.");

      _EraseWpt(wpt);
      _DrawWpt(wpt, true);
      this.selected = wpt;

      if (Shared.sett.user.map.centerSelectedMarker)
      {
        PointLatLng p = new GMap.NET.PointLatLng((double)wpt.lat, (double)wpt.lon);
        map.Position = p;
      }
    }

    private void _UnselectMarker()
    {
      if (this.selected != null)
      {
        _EraseWpt(this.selected);
        _DrawWpt(this.selected, false);
        this.selected = null;
      }
    }

    private void _EraseWpt(wptType wpt)
    {
      GMapMarker m = markers[wpt];
      if (m != null)
      {
        map.Markers.Remove(m);
        markers.Remove(wpt);
      }
    }

    private void _DrawWpt(wptType wpt, bool isSelected)
    {      
      GMapMarker marker = CreateMarker(wpt, isSelected);
      markers.Add(wpt, marker);
      map.Markers.Add(marker);
    }

    #endregion Map drawing methods

    private void cmbMapType_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
      if (map == null) return; // at init no map exist
      int index = cmbMapType.SelectedIndex;
      switch (index)
      {
        case 0:
          map.MapProvider = GMap.NET.MapProviders.BingMapProvider.Instance;
          break;
        case 1:
          map.MapProvider = GMap.NET.MapProviders.BingHybridMapProvider.Instance;
          break;
      }
    }

    internal void AimToPoints()
    {
      if (markers.Count == 0) return;

      ERectLatLng reqBnd = new ERectLatLng();
      bool isEmpty = true;

      foreach (var mrk in markers.Values)
      {
        var pos = mrk.Position;
        if (isEmpty)
        {
          reqBnd.Init(pos);
          isEmpty = false;
        }
        else
          reqBnd.Extend(pos);
      }

      var center = reqBnd.GetCenter();
      map.Position = center;

      int zoom = 1;
      while (true)
      {
        double prevZoom = map.Zoom;
        map.Zoom = zoom;
        if (map.Zoom != prevZoom && reqBnd.IsIn(map.ViewArea))
        {
          zoom++;

        }
        else
        {
          break;
        }
      }
      map.Zoom = map.Zoom - 1;
    }
  }
}
